/*
		ServEngine framework library
		A library to fast develop JEE online applications
	
		Copyright 2008 José Ignacio de Córdoba Álvaro

		Licensed under the Apache License, Version 2.0 (the "License");
		you may not use this file except in compliance with the License.
		You may obtain a copy of the License at

				http://www.apache.org/licenses/LICENSE-2.0

		Unless required by applicable law or agreed to in writing, software
		distributed under the License is distributed on an "AS IS" BASIS,
		WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
		See the License for the specific language governing permissions and
		limitations under the License.
	
		Jose Ignacio de Cordoba Alvaro
		http://ignacio.decordoba.com
*/

package com.servengine.struts;

import com.servengine.portal.Component;
import com.servengine.user.UserSessionSBean;

import com.servengine.user.UnidentifiedUserException;
import com.servengine.user.UserManagerLocal;

import com.servengine.util.ServletContextUtils;

import java.io.File;

import java.util.Locale;
import java.util.Map;

import java.security.AccessControlException;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.jsp.jstl.core.Config;

import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.chain.contexts.ActionContext;
import org.apache.struts.chain.contexts.ServletActionContext;
import org.apache.struts.config.ActionConfig;
import org.apache.struts.tiles.taglib.ComponentConstants;
import org.apache.struts.util.MessageResources;

/**
 * ServEngine uses Struts 1.3 chain action model, thus, substituing simple Struts Authorize action with this class.
 */
public class AuthorizeAction extends org.apache.struts.chain.commands.servlet.AuthorizeAction
{
	public static String USERSESSIONSBEAN_ATTRIBUTEID= "userSession";
	private static org.apache.commons.logging.Log log =		org.apache.commons.logging.LogFactory.getLog(AuthorizeAction.class.getName());

	/**
	 * Just in case you want to overcharge this method to make portalid permanent in a system with just one portal. Thant way links must not provide "portalid" request parameter.
	 * @param context
	 * @return
	 */
	protected String getPortalid(ServletActionContext context)
	{
		String portalid = (String)context.getParam().get("portalid");
		if (portalid == null)
			portalid = getPortalidfromcookies(context.getRequest());
		else
			setPortalidcookieinresponse(context.getResponse(),portalid);
		return portalid;
	}

	/**
	 * Comprueba si es UnidentifiedUserAction o AdminServiceAction
	 * @param action
	 * @param admin
	 * @return
	 */
	protected boolean checkSimpleInterfaceAccess(Component action, boolean admin)
	{
		try
		{
		  if (!Class.forName("com.servengine.struts.ClientAction").isAssignableFrom(Class.forName(action.getClassName())))
		    return true; //No es una clase de ServEngine, por ejemplo, la de tuportal.com para los Tiles a medida.
			if (action.isAdmin())
				return admin;
			return action.isGuest();
		} catch (Exception e)
		{
			throw new IllegalArgumentException(e.getMessage(),e);
		}
	}

	protected UserSessionSBean getUserSession(ServletActionContext context, UserManagerLocal usermanager)
	{
		UserSessionSBean userSession = (UserSessionSBean)context.getSessionScope().get(USERSESSIONSBEAN_ATTRIBUTEID);
		if (userSession == null || userSession.getPortalid() == null || userSession.getUserid() == null || (context.getRequest().getParameter("portalid") != null && !context.getRequest().getParameter("portalid").equals(userSession.getPortalid()))) // || user.getPkey()==null)
		{
			String portalid = getPortalid(context);
			if (portalid == null)
			{
				portalid = getServletContextUtils(context).getMainPortalid();//FIXME Cuidado en tuportal. Esto no vale.
				if (portalid==null)
				{
					includeError(context.getRequestScope(), "UnidentifiedPortalException");
					throw new IllegalStateException("UnidentifiedPortalException");
				}
			}
		  try
			{
			  if (context.getRequest().getCookies()!=null)
					for (Cookie cookie: context.getRequest().getCookies())
						if (cookie.getName().indexOf("servengineautologin#")==0 && cookie.getValue()!=null && cookie.getValue().length()>0 && portalid.equals(cookie.getName().substring(20)))
						{
							try
							{
								userSession = getServletContextUtils(context).getUserSessionSBean(usermanager.getNewUserSession(portalid,cookie.getValue()));
								userSession.setHasPicture(new java.io.File(getUserServicedir(context.getContext(),userSession.getPortalid()),userSession.getUser().getId()+".jpg").exists());
								com.servengine.user.UserUnidentifiedActions.doLoginCookies(userSession,userSession.getPortal().getSessionRememberDays(),context.getResponse());
							  if (log.isDebugEnabled())
									log.debug("User autologin :"+userSession.getUserid()+"@"+userSession.getPortalid());
							} catch (Throwable e) //TODO Verifica esto
							{ // Puede que el usuario ya no existe.
								log.warn("User erased, can't autologin for session "+cookie.getValue()+"@"+portalid);
								userSession=null;
							}
						}
				if (userSession == null || userSession.getPortalid() == null || userSession.getUserid() == null || (context.getRequest().getParameter("portalid") != null && !context.getRequest().getParameter("portalid").equals(userSession.getPortalid()))) // || user.getPkey()==null)
				{
				  if (log.isDebugEnabled())
						log.debug("Getting a new Guest session for "+portalid);
//					ServletContextUtils utils = ((ServletContextUtils)context.getApplicationScope().get(ServletContextUtils.SERVLETCONTEXT_ATTRIBUTE_NAME));
					//FIXME Si el portalid es de un portal que no existe da error indicando que el portal que no existe no tiene usuario guest.
					try
					{
						userSession = getServletContextUtils(context).getUserSessionSBean(usermanager.getGuestUserSession(portalid));
					} catch (Throwable e)
					{
						//if (log.isDebugEnabled())
							log.warn(e.getMessage(),e);
					  userSession = getServletContextUtils(context).getUserSessionSBean(usermanager.getGuestUserSession((getServletContextUtils(context).getMainPortalid())));
					}
				}
			} catch (IllegalArgumentException e)
			{
			  throw new IllegalArgumentException("UnidentifiedPortalException");
			} catch (Exception e)
			{
				throw new IllegalStateException(e);
			}
		  context.getSessionScope().put(USERSESSIONSBEAN_ATTRIBUTEID,userSession);
			Locale locale = userSession.getLocale();
			context.getSessionScope().put(Config.FMT_LOCALE, locale);
		  context.getSessionScope().put("javax.servlet.jsp.jstl.fmt.locale.session", locale);
			context.getSessionScope().put(ComponentConstants.LOCALE_KEY,locale);
		}
		return userSession;
	}

	protected File getUserServicedir(ServletContext servletcontext, String portalid) // TODO Unificar esto con ClientAction.getServiceDir()
	{
		String dirpath = servletcontext.getInitParameter("user.workdir");
		if (dirpath==null)
		{
			dirpath=servletcontext.getRealPath("/servicefiles/user/"+portalid);
			if (log.isDebugEnabled()) log.debug("No user.workdir specified in web.xml. Defaults to "+dirpath);
		}
		File dir = new File(dirpath);
		dir.mkdirs();
		return dir;
	}

	/**
	 * Overrides auth system in Struts using role and user ServEngine entity EJBs. Checks access to mapping and sets user attribute in request/session if needed.
	 * @param context
	 * @param roles
	 * @param mapping
	 * @return
	 */
	protected boolean isAuthorized(ActionContext context, java.lang.String[] roles, ActionConfig mapping)
	{
			ServletContextUtils utils = getServletContextUtils(context);
			UserManagerLocal usermanager = utils.getUserManager();
			UserSessionSBean session = getUserSession((ServletActionContext)context, usermanager);
			if (mapping.getType()==null)
			{
				if (log.isDebugEnabled()) log.debug("Authorizing direct mapping to "+mapping.getForward()+" @ "+mapping.getModuleConfig().getPrefix());
				return true;
			}
			Component action = utils.getPortalmanager().getAction(mapping.getType());//FIXME mirar la clase directamente o hacer un proxy para evitar llamada a la SSB
			if (action==null)//No es una acción registrada en el sistema.
			{
				log.warn("Non existing action, creating temp guest action: "+mapping.getType());
//				return false; Hay que continuar aunque no exista la clase, puede ser una acción de Tiles (tuportal.com)
				action = new Component(mapping.getType(), true, false);
			}
			if (checkSimpleInterfaceAccess(action, session.isAdmin()))
			{
				if (log.isDebugEnabled()) log.debug(session.getUser().getUserid()+"@"+session.getPortalid()+" directly authorized to " + mapping.getType());
				return true;
			}
			try
			{
				checkRoles(usermanager, session, action);
			} catch (UnidentifiedUserException e)
			{ //FIXME Aquí se mete "forward" que es lo mismo que el "requestURI" de ExceptionHandler. Quitar uno de los dos.
				HttpServletRequest request = ((ServletActionContext)context).getRequest();
				((ServletActionContext)context).getRequest().getSession().setAttribute("forward",request.getRequestURI()+(request.getQueryString()==null?"":"?"+request.getQueryString()));
				throw e;
			}
			if (log.isDebugEnabled())
				log.debug(session.getUserid() + "@" + session.getPortalid() + " authorized to " + mapping.getType());
			return true;
	}

	/**
	 * This code is placed here outside isAuthorized() method just in case needs to ve overriden.
	 * @param usermanager
	 * @param session
	 * @param action
	 */
	protected void checkRoles(UserManagerLocal usermanager, UserSessionSBean session, Component action)
	{
		if (!usermanager.checkActionAccess(session.getUser(), action))
		{
			if (log.isDebugEnabled())
				log.debug("Access for " + session.getUserid() + "@" + session.getPortalid() + " to " + action.getClassName()+" not granted");
			if (session.isGuest() || session.getUser().getUserid()==null || session.getUser().getUserid().length()==0)
				throw new UnidentifiedUserException(session.getUser().getUserid()+":"+action.getClassName());
			else
			{
				if (log.isDebugEnabled())
					log.debug("User " + session.getUser().getUserid() + "@" + session.getUser().getPortalid() + " can't access " + action.getClassName());
				throw new AccessControlException("User " + session.getUser().getUserid() + "@" + session.getUser().getPortalid() + " can't access " + action.getClassName());
			}
		}
	  if (log.isDebugEnabled())
	    log.debug("Access for " + session.getUserid() + "@" + session.getPortalid() + " to " + action.getClassName()+" granted");
	}

	/**
	 * Tries to guess portalid from cookies information
	 * @param request
	 * @return
	 */
	private String getPortalidfromcookies(HttpServletRequest request)
	{
		Cookie[] cookies = request.getCookies();
		if (cookies != null)
			for (int i = 0; i < cookies.length; i++)
				if ("portalid".equals(cookies[i].getName()))
					return cookies[i].getValue();
		return null;
	}

	final public void setPortalidcookieinresponse(HttpServletResponse res, String portalid)
	{
		try
		{
			res.addHeader("P3P","CP=\"CAO PSA OUR\"");
			Cookie cookie = new Cookie("portalid",portalid);
			cookie.setMaxAge(90*60*60*24); // Treinta días.
			cookie.setPath("/");
			res.addCookie(cookie);
		} catch (com.servengine.portal.UnidentifiedPortalException e) {
			if (log.isDebugEnabled()) log.debug(e.getMessage(),e);
		}
	}

	private void includeError(Map requestScope, String errorkey)
	{
		ActionMessages errors = new ActionMessages();
		errors.add("error", new ActionMessage(errorkey));
		requestScope.put(org.apache.struts.Globals.ERROR_KEY, errors);
	}

	protected String getErrorMessage(ActionContext context, ActionConfig actionConfig)
	{
		ServletActionContext servletActionContext = (ServletActionContext)context;
		// Retrieve internal message resources
		ActionServlet servlet = servletActionContext.getActionServlet();
		MessageResources resources = servlet.getInternal();
		return resources.getMessage("notAuthorized", actionConfig.getPath());
	}

	public boolean execute(ActionContext actionCtx) throws Exception
	{
		// Retrieve ActionConfig
		ActionConfig actionConfig = actionCtx.getActionConfig();
		// Is this action protected by role requirements?
		/*if (!isAuthorizationRequired(actionConfig)) {
					return (false);
			}*/
		return !(isAuthorized(actionCtx, actionConfig.getRoleNames(), actionConfig));
	}

	protected ServletContextUtils getServletContextUtils(ActionContext context)
	{
	  return ((ServletContextUtils)context.getApplicationScope().get(ServletContextUtils.SERVLETCONTEXT_ATTRIBUTE_NAME));
	}
}
